﻿namespace PowerSong.Projection
    {
    using System;
    using System.Collections.Generic;
    using System.Windows;
    using System.Windows.Controls;
    using System.Windows.Media;
    using System.Windows.Media.Animation;
    using System.Windows.Media.Imaging;
    using System.Windows.Shapes;

    public class Projector
        {

        public ProjectorWindow Window { get; internal set; }

        public Grid Grid { get; private set; }

        public Projectlet[] Projectlets { get; private set; }

        internal Projector(Grid grid, ProjectorWindow window)
            {
            grid.Background = Brushes.Black;
            this.Window = window;
            this.Grid = grid;
            this.Grid.SizeChanged += SurfaceSizeChanged;
            this.Projectlets = new Projectlet[] { };
            }

        private void SurfaceSizeChanged(object sender, SizeChangedEventArgs e)
            {
            foreach (var projectlet in Projectlets)
                {

                // Update the margins
                UpdateControlMargin(projectlet);

                // Update the font size
                if (projectlet.Type == ProjectletType.Text)
                    {
                    var control = (TextBlock)projectlet.Control;
                    if (control.Tag != null)
                        {
                        control.FontSize = CalculateFontSize(e.NewSize.Width, (double)control.Tag);
                        }
                    }

                }
            }

        /// <summary>
        /// Copies all of the projectlets from this projector manager to the given alternative projector manager.
        /// This method can be used to effectively clone the properties of one projector into another projector.
        /// </summary>
        internal void CopyTo(Projector manager)
            {

            foreach (var projectlet in Projectlets)
                {

                // Create the new projectlet by pushing a new one onto the projectlet stack
                var newProjectlet = manager.PushProjectlet(projectlet.Type, projectlet.Name);
                newProjectlet.FadeInTime = projectlet.FadeInTime;
                newProjectlet.FadeOutTime = projectlet.FadeOutTime;
                newProjectlet.CrossFadeTime = projectlet.CrossFadeTime;

                // Copy properties
                FrameworkElement genericOldControl = null;
                FrameworkElement genericNewControl = null;
                if (newProjectlet.Type == ProjectletType.Image)
                    {
                    var oldControl = (Image)projectlet.Control;
                    var newControl = (Image)newProjectlet.Control;
                    newControl.Source = oldControl.Source;
                    newControl.Stretch = oldControl.Stretch;
                    genericOldControl = oldControl;
                    genericNewControl = newControl;
                    }
                else if (newProjectlet.Type == ProjectletType.Text)
                    {
                    var oldControl = (TextBlock)projectlet.Control;
                    var newControl = (TextBlock)newProjectlet.Control;
                    newControl.Text = oldControl.Text;
                    newControl.Background = oldControl.Background;
                    newControl.Foreground = oldControl.Foreground;
                    newControl.FontFamily = oldControl.FontFamily;
                    newControl.FontStyle = oldControl.FontStyle;
                    newControl.FontWeight = oldControl.FontWeight;
                    newControl.FontSize = oldControl.FontSize;
                    newControl.TextWrapping = oldControl.TextWrapping;
                    newControl.TextAlignment = oldControl.TextAlignment;
                    newControl.TextDecorations = oldControl.TextDecorations;
                    genericOldControl = oldControl;
                    genericNewControl = newControl;
                    }
                else if (newProjectlet.Type == ProjectletType.Video)
                    {
                    var oldControl = (MediaElement)projectlet.Control;
                    var newControl = (MediaElement)newProjectlet.Control;
                    newControl.Source = oldControl.Source;
                    newControl.Volume = oldControl.Volume;
                    newControl.IsMuted = oldControl.IsMuted;
                    newControl.SpeedRatio = oldControl.SpeedRatio;
                    newControl.Position = oldControl.Position;
#warning Looping cannot be done without some minor cleverness!
                    //((MediaElement)newControl).MediaEnded += oldControl.MediaEnded;
                    genericOldControl = oldControl;
                    genericNewControl = newControl;
                    }
                else if (newProjectlet.Type == ProjectletType.Rectangle)
                    {
                    var oldControl = (Rectangle)projectlet.Control;
                    var newControl = (Rectangle)newProjectlet.Control;
                    newControl.Fill = ((Rectangle)oldControl).Fill;
                    genericOldControl = oldControl;
                    genericNewControl = newControl;
                    }
                else
                    {
                    throw new NotImplementedException("Not sure how to copy a particular type of projectlet.");
                    }

                // Copy other properties
                genericNewControl.Opacity = genericOldControl.Opacity;
                genericNewControl.Margin = genericOldControl.Margin;
                genericNewControl.Height = genericOldControl.Height;
                genericNewControl.Width = genericOldControl.Width;
                genericNewControl.Visibility = genericOldControl.Visibility;
                genericNewControl.HorizontalAlignment = genericOldControl.HorizontalAlignment;
                genericNewControl.VerticalAlignment = genericOldControl.VerticalAlignment;
                genericNewControl.Tag = genericOldControl.Tag;

                }

            }

        /// <summary>
        /// Creates a new projectlet of the given type and pushes it onto the stack.
        /// 
        /// The projectlet will be placed on top of all existing projectlets, so it's important to push them in the
        /// correct order as it is not possible to change the order of projectlets on the projectlet stack.
        /// </summary>
        /// <param name="type">The type of projectlet to create and push onto the projectlet stack.</param>
        /// <param name="name">The name of the projectlet, which must be given when you wish to modify its properties later.</param>
        /// <returns>The newly created projectlet.</returns>
        public Projectlet PushProjectlet(ProjectletType type, string name)
            {

            // Create the appropriate associated control
            var associatedControl = ControlFactory.CreateAssociatedControl(type);

            // Add the control to the projecting surface
            this.Grid.Children.Add(associatedControl);

            // Create the projectlet, associating the control with it
            var projectlet = new Projectlet(type, name, associatedControl);

            // Add to the list of projectlets
            var list = new List<Projectlet>(this.Projectlets);
            list.Add(projectlet);
            this.Projectlets = list.ToArray();

            // Return the projectlet that was created
            return projectlet;

            }

        private void Fade(FrameworkElement control,
                          double startOpacity,
                          double endOpacity,
                          TimeSpan duration,
                          bool deleteControlOnceDone = false)
            {

            // Don't fade if the control doesn't need fading
            if (control.Opacity == endOpacity) return;

            // Create the animation
            var animation = new DoubleAnimation();
            animation.From = startOpacity;
            animation.To = endOpacity;
            animation.Duration = new Duration(duration);

            // Target the opacity property
            Storyboard.SetTarget(animation, control);
            Storyboard.SetTargetProperty(animation, new PropertyPath(Control.OpacityProperty));

            // Start the animation
            var storyboard = new Storyboard();
            storyboard.FillBehavior = FillBehavior.Stop;
            storyboard.Completed += (a, b) =>
                {
                storyboard.Remove(control);
                if (deleteControlOnceDone) this.Grid.Children.Remove(control);
                control.Opacity = endOpacity;
                };
            storyboard.Children.Add(animation);
            storyboard.Begin(Grid);

            }

        public Projectlet GetProjectlet(string projectletName)
            {
            foreach (var projectlet in Projectlets)
                {
                if (projectlet.Name == projectletName) return projectlet;
                }
            return null;
            }

        private Projectlet GetProjectlet(string projectletName, ProjectletType type)
            {
            foreach (var projectlet in Projectlets)
                {
                if (projectlet.Name == projectletName && projectlet.Type == type) return projectlet;
                }
            return null;
            }

        private void FadeInternal(Projectlet projectlet, double startOpacity, double endOpacity, TimeSpan time)
            {
            Fade(projectlet.Control, startOpacity, endOpacity, time);
            }

        public void FadeIn(string projectletName)
            {
            var projectlet = GetProjectlet(projectletName);
            if (projectlet == null) throw new Exception("There is not a projectlet named '" + projectletName + "'.");
            FadeInternal(projectlet, 0.0, 1.0, projectlet.FadeInTime);
            }

        public void FadeInAll()
            {
            foreach (var projectlet in Projectlets)
                {
                FadeInternal(projectlet, 0.0, 1.0, projectlet.FadeInTime);
                }
            }

        public void FadeInAll(ProjectletType type)
            {
            foreach (var projectlet in Projectlets)
                {
                if (projectlet.Type == type)
                    {
                    FadeInternal(projectlet, 0.0, 1.0, projectlet.FadeInTime);
                    }
                }
            }

        public void FadeOut(string projectletName)
            {
            var projectlet = GetProjectlet(projectletName);
            if (projectlet == null) throw new Exception("There is not a projectlet named '" + projectletName + "'.");
            FadeInternal(projectlet, 1.0, 0.0, projectlet.FadeOutTime);
            }

        public void FadeOutAll()
            {
            foreach (var projectlet in Projectlets)
                {
                FadeInternal(projectlet, 1.0, 0.0, projectlet.FadeOutTime);
                }
            }

        public void FadeOutAll(ProjectletType type)
            {
            foreach (var projectlet in Projectlets)
                {
                if (projectlet.Type == type)
                    {
                    FadeInternal(projectlet, 1.0, 0.0, projectlet.FadeOutTime);
                    }
                }
            }

        public void Clear()
            {
            this.Grid.Children.Clear();
            this.Projectlets = new Projectlet[] { };
            }

        public void SetOpacityAll(double opacity)
            {
            foreach (var projectlet in Projectlets)
                {
                SetOpacity(projectlet.Name, opacity);
                }
            }

        public void SetOpacity(string projectletName, double opacity)
            {

            // Get the referenced projectlet by name
            var projectlet = GetProjectlet(projectletName);
            if (projectlet == null) throw new Exception("There does not exist a projectlet named '" + projectletName + "'.");

            // Make the necessary changes
            var control = (FrameworkElement)projectlet.Control;
            control.Opacity = opacity;

            }

        public void SetVisibility(string projectletName, Visibility visibility)
            {

            // Get the referenced projectlet by name
            var projectlet = GetProjectlet(projectletName);
            if (projectlet == null) throw new Exception("There does not exist a projectlet named '" + projectletName + "'.");

            // Make the necessary changes
            var control = (FrameworkElement)projectlet.Control;
            control.Visibility = visibility;

            }

        public void SetImage(string imageProjectletName, string fileName)
            {

            // Get the referenced projectlet by name
            var projectlet = GetProjectlet(imageProjectletName, ProjectletType.Image);
            if (projectlet == null) throw new Exception("There does not exist an image projectlet named '" + imageProjectletName + "'.");

            // Make the necessary changes
            var control = (Image)projectlet.Control;
            var uri = new Uri(fileName, UriKind.RelativeOrAbsolute);
            control.Source = new BitmapImage(uri);
            control.Stretch = Stretch.Fill;

            }

        public void SetAlignment(string projectletName,
                                 HorizontalAlignment horizontalAlignment,
                                 VerticalAlignment verticalAlignment)
            {

            // Get the referenced projectlet by name
            var projectlet = GetProjectlet(projectletName);
            if (projectlet == null) throw new Exception("There does not exist a projectlet named '" + projectletName + "'.");

            // Make the necessary changes
            var control = (FrameworkElement)projectlet.Control;
            control.HorizontalAlignment = horizontalAlignment;
            control.VerticalAlignment = verticalAlignment;

            }

        public void SetMargin(string projectletName,
                              double marginLeft,
                              double marginTop,
                              double marginRight,
                              double marginBottom)
            {

            // Get the referenced projectlet by name
            var projectlet = GetProjectlet(projectletName);
            if (projectlet == null) throw new Exception("There does not exist a projectlet named '" + projectletName + "'.");

            // Make the necessary changes
            projectlet.Margin = new Thickness(marginLeft, marginTop, marginRight, marginBottom);
            UpdateControlMargin(projectlet);

            }

        private void UpdateControlMargin(Projectlet projectlet)
            {
            var control = (FrameworkElement)projectlet.Control;
            control.Margin = new Thickness(projectlet.Margin.Left * Grid.ActualWidth,
                                           projectlet.Margin.Top * Grid.ActualHeight,
                                           projectlet.Margin.Right * Grid.ActualWidth,
                                           projectlet.Margin.Bottom * Grid.ActualHeight);
            }

        public void SetStyle(string textProjectletName,
                             Brush backgroundBrush,
                             Brush foregroundBrush,
                             string fontName,
                             TextAlignment textAlignment,
                             TextWrapping textWrapping,
                             double fontSizeMultiplier,                 
                             FontWeight fontWeight, 
                             FontStyle fontStyle,
                             TextDecorationCollection fontDecorations)
            {

            // Get the referenced projectlet by name
            var projectlet = GetProjectlet(textProjectletName, ProjectletType.Text);
            if (projectlet == null) throw new Exception("There does not exist a text projectlet named '" + textProjectletName + "'.");

            // Make the necessary changes
            var control = (TextBlock)projectlet.Control;
            control.Tag = fontSizeMultiplier;
            control.Background = backgroundBrush;
            control.Foreground = foregroundBrush;
            control.FontFamily = new FontFamily(fontName);
            control.FontStyle = fontStyle;
            control.FontWeight = fontWeight;
            control.TextDecorations = fontDecorations;
#warning Decide what to do about font size - what does it really represent? Fix the imports too.
            control.FontSize = CalculateFontSize(Grid.ActualWidth, fontSizeMultiplier);
            control.TextWrapping = textWrapping;
            control.TextAlignment = textAlignment;

            }

        private double CalculateFontSize(double surfaceWidth, double fontSizeMultiplier)
            {
            return surfaceWidth == 0 ? fontSizeMultiplier * 50 : fontSizeMultiplier * (surfaceWidth / 10);
            }

        public void SetText(string textProjectletName, string text)
            {

            // Get the referenced projectlet by name
            var projectlet = GetProjectlet(textProjectletName, ProjectletType.Text);
            if (projectlet == null) throw new Exception("There does not exist a text projectlet named '" + textProjectletName + "'.");

            // Make the necessary changes
            var control = (TextBlock)projectlet.Control;
            control.Text = text;

            }

        public void CrossFadeText(string projectletName, string newText)
            {

            // Get the projectlet
            var projectlet = GetProjectlet(projectletName);
            if (projectlet == null) throw new Exception("There is not a projectlet named '" + projectletName + "'.");
            if (projectlet.Type != ProjectletType.Text) throw new Exception("The projectlet named '" + projectletName + "' is not a textual projectlet.");

            // Fade in the existing control with the new text
            var existingControl = (TextBlock)projectlet.Control;
            var previousText = existingControl.Text;
            var currentOpacity = existingControl.Opacity;
            existingControl.Opacity = 0;
            existingControl.Text = newText;
            Fade(existingControl, 0.0, 1.0, projectlet.CrossFadeTime);

            // Create a temporary control holding the "old" text that will be faded out and ultimately deleted
#warning Seems quite verbose this bit. Is there anything that can be done to improve this code?
            var tempControl = (TextBlock)ControlFactory.CreateAssociatedControl(ProjectletType.Text);
            tempControl.Text = previousText;
            tempControl.Margin = existingControl.Margin;
            tempControl.Padding = existingControl.Padding;
            tempControl.HorizontalAlignment = existingControl.HorizontalAlignment;
            tempControl.VerticalAlignment = existingControl.VerticalAlignment;
            tempControl.Foreground = existingControl.Foreground;
            tempControl.Background = existingControl.Background;
            tempControl.FontFamily = existingControl.FontFamily;
            tempControl.FontStyle = existingControl.FontStyle;
            tempControl.FontWeight = existingControl.FontWeight;
            tempControl.FontSize = existingControl.FontSize;
            tempControl.TextWrapping = existingControl.TextWrapping;
            tempControl.TextAlignment = existingControl.TextAlignment;
            tempControl.TextDecorations = existingControl.TextDecorations;
            tempControl.Tag = existingControl.Tag;
            this.Grid.Children.Add(tempControl);
            Fade(tempControl, currentOpacity, 0.0, projectlet.CrossFadeTime, true);

            }

        public void CrossFadeRectangle(string projectletName, Color newColour)
            {

            // Get the projectlet
            var projectlet = GetProjectlet(projectletName);
            if (projectlet == null) throw new Exception("There is not a projectlet named '" + projectletName + "'.");
            if (projectlet.Type != ProjectletType.Rectangle) throw new Exception("The projectlet named '" + projectletName + "' is not a rectangle projectlet.");

            throw new NotImplementedException();

            }

        public void CrossFadeImage(string projectletName, string newImageFileName)
            {

            // Get the projectlet
            var projectlet = GetProjectlet(projectletName);
            if (projectlet == null) throw new Exception("There is not a projectlet named '" + projectletName + "'.");
            if (projectlet.Type != ProjectletType.Image) throw new Exception("The projectlet named '" + projectletName + "' is not an image projectlet.");

            throw new NotImplementedException();

            }

        public void CrossFadeVideo(string projectletName, string newVideoFileName)
            {

            // Get the projectlet
            var projectlet = GetProjectlet(projectletName);
            if (projectlet == null) throw new Exception("There is not a projectlet named '" + projectletName + "'.");
            if (projectlet.Type != ProjectletType.Video) throw new Exception("The projectlet named '" + projectletName + "' is not a video projectlet.");

            throw new NotImplementedException();

            }

        public void SetVideoDetails(string videoProjectletName, string fileName, TimeSpan startPosition, double volume, double speed, bool loop)
            {

            // Get the referenced projectlet by name
            var projectlet = GetProjectlet(videoProjectletName, ProjectletType.Video);
            if (projectlet == null) throw new Exception("There does not exist a video projectlet named '" + videoProjectletName + "'.");

            // Make the necessary changes
            var control = (MediaElement)projectlet.Control;
            control.Source = new Uri(fileName, UriKind.RelativeOrAbsolute);
            control.SpeedRatio = speed;
            control.Volume = volume;
            control.IsMuted = volume == 0;
            control.Position = startPosition;
            if (loop == true) 
                {
                control.MediaEnded += (object sender, RoutedEventArgs e) =>
                    {
                    var mediaControl = (MediaElement)sender;
                    mediaControl.Position = startPosition;
                    mediaControl.Play();
                    };
                }
            }

        public void PlayVideo(string videoProjectletName)
            {

            // Get the referenced projectlet by name
            var projectlet = GetProjectlet(videoProjectletName, ProjectletType.Video);
            if (projectlet == null) throw new Exception("There does not exist a video projectlet named '" + videoProjectletName + "'.");

            // Make the necessary changes
            var control = (MediaElement)projectlet.Control;
            control.Play();

            }

        public void StopVideo(string videoProjectletName)
            {

            // Get the referenced projectlet by name
            var projectlet = GetProjectlet(videoProjectletName, ProjectletType.Video);
            if (projectlet == null) throw new Exception("There does not exist a video projectlet named '" + videoProjectletName + "'.");

            // Make the necessary changes
            var control = (MediaElement)projectlet.Control;
            control.Stop();

            }

        public void PauseVideo(string videoProjectletName)
            {

            // Get the referenced projectlet by name
            var projectlet = GetProjectlet(videoProjectletName, ProjectletType.Video);
            if (projectlet == null) throw new Exception("There does not exist a video projectlet named '" + videoProjectletName + "'.");

            // Make the necessary changes
            var control = (MediaElement)projectlet.Control;
            control.Pause();

            }

        public void SetVideoVolume(string videoProjectletName, double volume)
            {

            // Get the referenced projectlet by name
            var projectlet = GetProjectlet(videoProjectletName, ProjectletType.Video);
            if (projectlet == null) throw new Exception("There does not exist a video projectlet named '" + videoProjectletName + "'.");

            // Make the necessary changes
            var control = (MediaElement)projectlet.Control;
            control.Volume = volume;
            control.IsMuted = volume == 0;

            }

        public void SetBackground(string rectangleProjectletName, Color color)
            {
            
            // Get the referenced projectlet by name
            var projectlet = GetProjectlet(rectangleProjectletName, ProjectletType.Rectangle);
            if (projectlet == null) throw new Exception("There does not exist a rectangle projectlet named '" + rectangleProjectletName + "'.");

            // Make the necessary changes
            var control = (Rectangle)projectlet.Control;
            control.Fill = new SolidColorBrush(color);

            }

        }

    }
